perm filename MACROS.L[FTL,LSP] blob
sn#826368 filedate 1986-10-21 generic text, type T, neo UTF8
;;;-*-Mode:LISP; Package:(PCL (LISP WALKER)); Base:10; Syntax:Common-lisp -*-
;;;
;;; *************************************************************************
;;; Copyright (c) 1985 Xerox Corporation. All rights reserved.
;;;
;;; Use and copying of this software and preparation of derivative works
;;; based upon this software are permitted. Any distribution of this
;;; software or derivative works must comply with all applicable United
;;; States export control laws.
;;;
;;; This software is made available AS IS, and Xerox Corporation makes no
;;; warranty about the software, its performance or its conformity to any
;;; specification.
;;;
;;; Any person obtaining a copy of this software is requested to send their
;;; name and post office or electronic mail address to:
;;; CommonLoops Coordinator
;;; Xerox Artifical Intelligence Systems
;;; 2400 Hanover St.
;;; Palo Alto, CA 94303
;;; (or send Arpanet mail to CommonLoops-Coordinator.pa@Xerox.arpa)
;;;
;;; Suggestions, comments and requests for improvements are also welcome.
;;; *************************************************************************
;;;
;;; Macros global variable definitions, and other random support stuff used
;;; by the rest of the system.
;;;
;;; For simplicity (not having to use eval-when a lot), this file must be
;;; loaded before it can be compiled.
;;;
(in-package 'pcl :nicknames '(portable-commonloops) :use '(lisp walker))
(export '(ndefstruct
defclass
defmeth
run-super
make
initialize
get-slot
with
with*
class-of
class-named
discriminator-named
class-prototype
class
object
essential-class
class-name
class-precedence-list
class-local-supers
class-local-slots
class-direct-subclasses
class-direct-methods
class-slots
essential-discriminator
discriminator-name
discriminator-methods
discriminator-discriminating-function
essential-method
method-discriminator
method-arglist
method-argument-specifiers
method-function
method-equal
discriminator-methods
slotd-name
slot-missing
define-meta-class
%make-instance
%instance-ref
%instancep
%instance-meta-class
make-instance
get-slot
put-slot
get-slot-using-class
optimize-slot-access
define-class-of-clause
add-named-class
class-for-redefinition
add-class
supers-changed
slots-changed
check-super-meta-class-compatibility
check-meta-class-change-compatibility
make-slotd
compute-class-precedence-list
walk-method-body
walk-method-body-form
optimize-get-slot
optimize-set-of-get-slot
variable-lexical-p
add-named-method
add-method
remove-named-method
remove-method
find-method
find-method-internal
make-discriminating-function
install-discriminating-function
no-matching-method
class-class-precedence-list
class-local-supers
class-direct-subclasses
class-name
)
(find-package 'pcl))
(proclaim '(declaration values ;I use this so that Zwei will remind
;what values a function returns.
))
;;; Age old functions which CommonLisp cleaned-up away. They probably exist
;;; in other packages in all CommonLisp implementations, but I will leave it
;;; to the compiler to optimize into calls to them.
;;;
;;; Common Lisp BUG:
;;; Some Common Lisps define these in the Lisp package which causes
;;; all sorts of lossage. Common Lisp should explictly specify which
;;; symbols appear in the Lisp package.
;;;
(defmacro memq (item list) `(member ,item ,list :test #'eq))
(defmacro assq (item list) `(assoc ,item ,list :test #'eq))
(defmacro delq (item list) `(delete ,item ,list :test #'eq))
#-Xerox
(defmacro neq (x y) `(not (eq ,x ,y)))
(defun make-caxr (n form)
(if (< n 4)
`(,(nth n '(car cadr caddr cadddr)) ,form)
(make-caxr (- n 4) `(cddddr ,form))))
(defun make-cdxr (n form)
(cond ((zerop n) form)
((< n 5) `(,(nth n '(identity cdr cddr cdddr cddddr)) ,form))
(t (make-cdxr (- n 4) `(cddddr ,form)))))
(defmacro ignore (&rest vars)
#+Symbolics `(progn . ,(remove 'ignore vars))
#-Symbolics `(declare (ignore . ,vars)))
(defun true (&rest ignore) (ignore ignore) t)
(defun false (&rest ignore) (ignore ignore) nil)
;;; ONCE-ONLY does the same thing as it does in zetalisp. I should have just
;;; lifted it from there but I am honest. Not only that but this one is
;;; written in Common Lisp. I feel a lot like bootstrapping, or maybe more
;;; like rebuilding Rome.
(defmacro once-only (vars &body body)
(let ((gensym-var (gensym))
(run-time-vars (gensym))
(run-time-vals (gensym))
(expand-time-val-forms ()))
(dolist (var vars)
(push `(if (or (symbolp ,var)
(numberp ,var)
(and (listp ,var)
(member (car ,var) '(quote function))))
,var
(let ((,gensym-var (gensym)))
(push ,gensym-var ,run-time-vars)
(push ,var ,run-time-vals)
,gensym-var))
expand-time-val-forms))
`(let* (,run-time-vars
,run-time-vals
(wrapped-body
((lambda ,vars . ,body) . ,(reverse expand-time-val-forms))))
`((lambda ,(nreverse ,run-time-vars) ,wrapped-body)
. ,(nreverse ,run-time-vals)))))
(defun extract-declarations (body)
(declare (values documentation declares body))
(let (documentation declares)
(when (stringp (car body)) (setq documentation (pop body)))
(do ((form (car body) (car body)))
((or (null body)
(not (and (listp form) (eq (car form) 'declare))))
(values documentation declares body))
(push (pop body) declares))))
;;
;;;;;; FAST-NCONC Lists
;;
;;; These are based on Interlisp's TCONC function. They are slighlty
;;; generalized to take either the item to nconc onto the end of the list or
;;; a cons to add to the end of a list. In addition there is a constructor to
;;; make fast-nconc-lists and an accessor to get at a fast-nconc-list's real
;;; list.
(defmacro make-fast-nconc-list ()
`(let ((fast-nconc-list (cons () (list ()))))
(rplaca fast-nconc-list (cdr fast-nconc-list))
fast-nconc-list))
(defmacro fast-nconc-list-real-list (fast-nconc-list)
`(cddr ,fast-nconc-list))
(defmacro fast-nconc-cons (fast-nconc-list cons)
(once-only (fast-nconc-list)
`(progn (rplacd (car ,fast-nconc-list) ,cons)
(rplaca ,fast-nconc-list (cdar ,fast-nconc-list)))))
(defmacro fast-nconc-item (fast-nconc-list item)
`(fast-nconc-cons ,fast-nconc-list (cons ,item nil)))
#-Xerox
(defun make-keyword (symbol)
(intern (symbol-name symbol) (find-package 'keyword)))
(defun string-append (&rest strings)
(do ((string-loc strings (cdr string-loc)))
((null string-loc)
(apply #'concatenate 'string strings))
(rplaca string-loc (string (car string-loc)))))
(defun symbol-append (sym1 sym2 &optional (package *package*))
(intern (string-append sym1 sym2) package))
(defmacro check-member (place list &key (test #'eql) (pretty-name place))
(once-only (place list)
`(or (member ,place ,list :test ,test)
(error "The value of ~A, ~S is not one of ~S."
',pretty-name ,place ,list))))
;;; A simple version of destructuring-bind.
;;; This does no more error checking than CAR and CDR themselves do. Some
;;; attempt is made to be smart about preserving intermediate values. It
;;; could be better, although the only remaining case should be easy for
;;; the compiler to spot since it compiles to PUSH POP.
;;;
;;; Common Lisp BUG:
;;; Common Lisp should have destructuring-bind.
;;;
(defmacro destructuring-bind (pattern form &body body)
(multiple-value-bind (ignore declares body)
(extract-declarations body)
(multiple-value-bind (setqs binds)
(destructure pattern form)
`(let ,binds
,@declares
,@setqs
. ,body))))
(defun destructure (pattern form)
(declare (values setqs binds))
(let ((*destructure-vars* ())
(setqs ()))
(declare (special *destructure-vars*))
(when (not (symbolp form))
(setq *destructure-vars* '(.destructure-form.)
setqs (list `(setq .destructure-form. ,form)))
(setq form '.destructure-form.))
(values (nconc setqs (nreverse (destructure-internal pattern form)))
(delete nil *destructure-vars*))))
(defun destructure-internal (pattern form)
;; When we are called, pattern must be a list. Form should be a symbol
;; which we are free to setq containing the value to be destructured.
;; Optimizations are performed for the last element of pattern cases.
;; we assume that the compiler is smart about gensyms which are bound
;; but only for a short period of time.
(declare (special *destructure-vars*))
(let ((gensym (gensym))
(pending-pops 0)
(var nil)
(setqs ()))
(labels
((make-pop (var form pop-into)
(prog1
(cond ((zerop pending-pops)
`(progn ,(and var `(setq ,var (car ,form)))
,(and pop-into `(setq ,pop-into (cdr ,form)))))
((null pop-into)
(and var `(setq ,var ,(make-caxr pending-pops form))))
(t
`(progn (setq ,pop-into ,(make-cdxr pending-pops form))
,(and var `(setq ,var (pop ,pop-into))))))
(setq pending-pops 0))))
(do ((pat pattern (cdr pat)))
((null pat) ())
(if (symbolp (setq var (car pat)))
(progn
(push var *destructure-vars*)
(cond ((null (cdr pat))
(push (make-pop var form ()) setqs))
((symbolp (cdr pat))
(push (make-pop var form (cdr pat)) setqs)
(push (cdr pat) *destructure-vars*)
(return ()))
((memq var '(nil ignore)) (incf pending-pops))
((memq (cadr pat) '(nil ignore))
(push (make-pop var form ()) setqs)
(incf pending-pops 1))
(t
(push (make-pop var form form) setqs))))
(progn
(push `(let ((,gensym ()))
,(make-pop gensym form (if (symbolp (cdr pat)) (cdr pat) form))
,@(nreverse
(destructure-internal (if (consp pat) (car pat) pat)
gensym)))
setqs)
(when (symbolp (cdr pat))
(push (cdr pat) *destructure-vars*)
(return)))))
setqs)))
;;; Iterate is a simple iteration macro. If CommonLisp had a standard Loop
;;; macro I wouldn't need this wretched crock. But what the hell, it seems
;;; to do most of what I need. It looks like:
;;; (iterate (<control-clause-1> <control-clause-2> ...)
;;; . <body>)
;;;
;;; a control clause can be one of:
;;; (<var> in <list-form>) | (<var> in <list-form> by <function>)
;;; (<var> on <list-form>) | (<var> on <list-form> by <function>)
;;; (<var> from <initial> to <final>)
;;; (<var> from <initial> below <final>)
;;; (<var> from <initial> to <final> by <function> | <increment>)
;;; (<var> from <initial> below <final> by <function> | <increment>)
;;; (<var> = <form>) <form> is evaluated each time through
;;; (<var> = <initial> <subsequent>)
;;;
;;; inside <body> you are allowed to use:
;;; collect
;;; join
;;; sum
(defvar *iterate-result-types* ())
(defmacro define-iterate-result-type (name arglist &body body)
(let ((fn-name
(if (and (null (cdr body)) (symbolp (car body)))
(car body)
(make-symbol (string-append (symbol-name name) " iterate result type")))))
`(progn
(let ((existing (assq ',name *iterate-result-types*)))
(if existing
(rplacd existing ',fn-name)
(push ',(cons name fn-name) *iterate-result-types*)))
,(and (not (and (null (cdr body)) (symbolp (car body))))
`(defun ,fn-name ,arglist . ,body)))))
(defmacro iterate (controls &body body)
#+Xerox (setq body (copy-tree body))
(let (binds var-init-steps
pre-end-tests post-end-tests
pre-bodies post-bodies
(result-type ()))
(mapc #'(lambda (control)
(let ((var (car control))
(type (cadr control))
(initial (caddr control))
(args (cdddr control)))
(ecase type
((in on)
(let* ((gensym (if (or (eq type 'in) (consp var)) (gensym) var))
(step `(,(if args (cadr args) 'cdr) ,gensym)))
(push `(,gensym ,initial ,step) var-init-steps)
(push `(null ,gensym) pre-end-tests)
(cond ((listp var)
(multiple-value-bind (setqs dbinds)
(destructure var (if (eq type 'in) `(car ,gensym) gensym))
(setq binds (nconc dbinds binds))
(setq pre-bodies (nconc pre-bodies (nreverse setqs)))))
((eq type 'in)
(push var binds)
(push `(setq ,var (car ,gensym)) pre-bodies)))))
(from
(let ((gensym (gensym))
(final
(and (memq (car args) '(to below))
(if (eq (car args) 'to)
(cadr args)
`(- ,(cadr args) 1))))
(step
(progn (setq args (member 'by args))
(cond ((null args)
`(1+ ,var))
((numberp (cadr args))
`(+ ,var ,(cadr args)))
(t (cadr args))))))
(push `(,var ,initial ,step) var-init-steps)
(and final (push `(,gensym ,final) binds))
(and final (push `(> , var ,gensym) pre-end-tests))))
(=
(push `(,var ,initial ,(or (car args) initial))
var-init-steps))
)))
controls)
(setq body
(walk-form (cons 'progn body)
:walk-function
#'(lambda (form context &aux aux)
(ignore context)
(or (and (listp form)
(setq aux (assq (car form) *iterate-result-types*))
(setq result-type
(if (null result-type)
(funcall (cdr aux)
form nil 'create-result-type)
(funcall (cdr aux)
form result-type 'check-result-type)))
(funcall (cdr aux) form result-type 'macroexpand))
form))))
(let* ((initially (cons 'progn
(dolist (tlf body)
(when (and (consp tlf) (eq (car tlf) 'initially))
(return (prog1 (cdr tlf)
(setf (car tlf) 'progn
(cdr tlf) ())))))))
(finally (cons 'progn
(dolist (tlf body)
(when (and (consp tlf) (eq (car tlf) 'finally))
(return (prog1 (cdr tlf)
(setf (car tlf) 'progn
(cdr tlf) ()))))))))
`(let (,@binds . ,(caddr result-type))
(iterate-macrolets
(prog ,(mapcar #'(lambda (x) (list (car x) (cadr x)))
var-init-steps)
,initially
restart
(and (or . ,(reverse pre-end-tests))
(go .iterate←return.))
(progn . ,(reverse pre-bodies))
,body
(progn . ,(reverse post-bodies))
(or ,@post-end-tests
(progn ,@(mapcar #'(lambda (x)
(and (cddr x)
`(setq ,(car x)
,(caddr x))))
var-init-steps)
(go restart)))
.iterate←return.
,finally
(return ,(cadddr result-type))))))))
(define-iterate-result-type collect (form result-type op)
iterate-collect-join)
(define-iterate-result-type join (form result-type op)
iterate-collect-join)
(defun iterate-collect-join (form result-type op)
(ecase op
(create-result-type
(let ((gensym (gensym)))
`(,(car form) ,gensym ((,gensym ())) (nreverse ,gensym))))
(check-result-type
(if (memq (car result-type) '(collect join))
result-type
(error "Using ~S inside an iterate in which you already used ~S."
(car form) (car result-type))))
(macroexpand
(if (eq (car form) 'collect)
`(push ,(cadr form) ,(cadr result-type))
`(setq ,(cadr result-type)
(append (reverse ,(cadr form)) ,(cadr result-type)))))))
(define-iterate-result-type sum (form result-type op)
(ecase op
(create-result-type
(let ((gensym (gensym)))
`(,(car form) ,gensym ((,gensym 0)) ,gensym)))
(check-result-type
(eq (car result-type) 'sum))
(macroexpand
`(incf ,(cadr result-type) ,(cadr form)))))
(defmacro iterate-macrolets (&body body)
`(macrolet
((until (test)
`(when ,test (go .iterate←return.)))
(while (test)
`(until (not ,test)))
(initially (&body body)
(error
"It is an error for FINALLY to appear other than at top-level~%~
inside an iterate."))
(finally (&body ignore)
(error
"It is an error for INITIALLY to appear other than at top-level~%~
inside an iterate."))
)
. ,body))
;;;
;;; Two macros useful for parsing defstructs.
;;; The first parses slot-description (or lambda-list) style keyword-value
;;; pairs. The second, more complicated one, parses defstruct option style
;;; keyword-value pairs.
;;;
(defmacro keyword-bind (keywords form &body body)
`(apply (function (lambda (&key . ,keywords) . ,body)) ,form))
;;;
;;; (keyword-parse (<keyword-spec-1> <keyword-spec-2> ..)
;;; form
;;; . body)
;;;
;;; Where form is a form which will be evaluated and should return the list
;;; of keywords and values which keyword-parse will parse. Body will be
;;; evaluated with the variables specified by the keyword-specs bound.
;;; Keyword specs look like:
;;; <var>
;;; (<var> <default>)
;;; (<var> <default> <suppliedp var>)
;;; (<var> <default> <suppliedp var> <option-1> <val-1> ...)
;;;
;;; The options can be:
;;; :allowed --- :required :multiple
;;; :return-cdr --- t nil
;;;
(defmacro keyword-parse (keywords form &body body)
;; This makes an effort to resemble keyword-bind in that the vars are bound
;; one at a time so that a default value form can look at the value of a
;; previous argument. This is probably more hair than its worth, but what
;; the hell, programming is fun.
(let* ((lambda-list ())
(supplied-p-gensyms ())
(value-forms ())
(entry-var (gensym)))
(dolist (kw keywords)
(unless (listp kw) (setq kw (list kw)))
(destructuring-bind (var default supplied-p-var . options) kw
(keyword-bind (presence (allowed ':required) return-cdr) options
(push var lambda-list)
(when supplied-p-var
(push supplied-p-var lambda-list)
(push (gensym) supplied-p-gensyms))
(push `(let ((,entry-var (keyword-parse-assq ',(make-keyword var)
,form
',allowed)))
(if (null ,entry-var)
,default
;; Insert appropriate error-checking based on the
;; allowed argument.
(progn
,(when (null allowed)
`(unless (nlistp (car ,entry-var))
(error "The ~S keyword was supplied with an~
argument, it is not allowed to have one."
',(make-keyword var))))
,(when (eq allowed ':required)
`(unless (listp (car ,entry-var))
(error
"The ~S keyword was supplied without an~
argument~%when present, this keyword must~
have an argument."
',(make-keyword var))))
(cond ((listp (car ,entry-var))
,(and supplied-p-var
`(setq ,(car supplied-p-gensyms) 't))
,(if return-cdr
(if (eq allowed ':multiple)
`(mapcar #'cdr ,entry-var)
`(cdar ,entry-var))
(if (eq allowed ':multiple)
`(mapcar #'cadr ,entry-var)
`(cadar ,entry-var))))
(t
,(and supplied-p-var
`(setq ,(car supplied-p-gensyms)
':presence))
,presence)))))
value-forms)
(when supplied-p-var
(push (car supplied-p-gensyms) value-forms)))))
`(let ,supplied-p-gensyms
((lambda ,(reverse lambda-list) . ,body) . ,(reverse value-forms)))))
(defun keyword-parse-assq (symbol list allowed)
(do ((result nil result)
(tail list (cdr tail)))
((null tail) (nreverse result))
(if (eq (if (symbolp (car tail)) (car tail) (caar tail)) symbol)
(if (neq allowed ':multiple)
(return tail)
(push (car tail) result)))))
;;
;;;;;; printing-random-thing
;;
;;; Similar to printing-random-object in the lisp machine but much simpler
;;; and machine independent.
(defmacro printing-random-thing ((thing stream) &body body)
(once-only (stream)
`(let ((*print-level* (and (numberp *print-level*) (- *print-level* 1))))
(progn (princ "#<" ,stream)
,@body
(princ " " ,stream)
(printing-random-thing-internal ,thing ,stream)
(princ ">" ,stream)))))
(defun printing-random-thing-internal (thing stream)
(declare (ignore thing stream))
nil)
;;
;;;;;;
;;
(defun capitalize-words (string)
(setq string (copy-seq (string string)))
(do* ((flag t flag)
(length (length string) length)
(char nil char)
(i 0 (+ i 1)))
((= i length) string)
(declare (string string))
(setq char (elt string i))
(cond ((both-case-p char)
(if flag
(and (setq flag (lower-case-p char))
(setf (elt string i) (char-upcase char)))
(and (not flag) (setf (elt string i) (char-downcase char))))
(setq flag nil))
((char-equal char #\-)
(setq flag t))
(t (setq flag nil)))))
;;
;;;;;; CLASS-NAMED naming classes.
;;
;;;
;;; (CLASS-NAMED <name>) returns the class named <name>. setf can be used
;;; with class-named to set the class named <name>. These are "extrinsic"
;;; names. Neither class-named nor setf of class-named do anything with the
;;; name slot of the class, they only lookup and change the association from
;;; name to class.
;;;
(defvar *class-name-hash-table* (make-hash-table :test #'eq))
(defun class-named (name &optional no-error-p)
(or (gethash name *class-name-hash-table*)
(if no-error-p () (error "No class named: ~S." name))))
(defsetf class-named (name &optional ignore-damnit) (class)
`(setf (gethash ,name *class-name-hash-table*) ,class))
(defvar *discriminator-name-hash-table* (make-hash-table :test #'eq
:size 1000))
(defun discriminator-named (name) ;This a function for
(gethash name *discriminator-name-hash-table*)) ;the benefit of
;compile-time-define?
(defsetf discriminator-named (name) (new-value)
`(setf (gethash ,name *discriminator-name-hash-table*) ,new-value))
;;
;;;;;; Special variable definitions.
;;
;;; Gets set to its right value once early-defmeths are fixed.
;;;
(defvar *error-when-defining-